/**
 * @file STL_MysqlConnPool.cpp
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief 
 * @version 0.1
 * @date 2020-10-14
 * 
 * @copyright Copyright Sci-Tech Laboratories (c) 2020
 * 
 */

#include "STL_MysqlConnPool.h"
#include <stdlib.h>

// for DEBUG
using std::cout;
using std::endl;

namespace STL
{
    // static field init
    std::string STL_MysqlConnPool::host_ = "";
    unsigned int STL_MysqlConnPool::port_ = 3306;
    std::string STL_MysqlConnPool::user_ = "";
    std::string STL_MysqlConnPool::passwd_ = "";
    std::string STL_MysqlConnPool::database_ = "";
    int STL_MysqlConnPool::poolSize_ = 0;
    STL_MysqlConnPool *STL_MysqlConnPool::pool_ = NULL;
    pthread_mutex_t STL_MysqlConnPool::mutex;
    pthread_mutex_t STL_MysqlConnPool::execmutex;
    pthread_mutex_t STL_MysqlConnPool::initmutex;
    pthread_mutex_t STL_MysqlConnPool::logmutex;

    void STL_MysqlConnPool::lock()
    {
        pthread_mutex_lock(&mutex);
    }

    void STL_MysqlConnPool::unlock()
    {
        pthread_mutex_unlock(&mutex);
    }

    void STL_MysqlConnPool::locke()
    {
        pthread_mutex_lock(&execmutex);
    }

    void STL_MysqlConnPool::unlocke()
    {
        pthread_mutex_unlock(&execmutex);
    }

    void STL_MysqlConnPool::locki()
    {
        pthread_mutex_lock(&initmutex);
    }

    void STL_MysqlConnPool::unlocki()
    {
        pthread_mutex_unlock(&initmutex);
    }

    void STL_MysqlConnPool::lockl()
    {
        pthread_mutex_lock(&logmutex);
    }

    void STL_MysqlConnPool::unlockl()
    {
        pthread_mutex_unlock(&logmutex);
    }

    STL_MysqlConnPool::STL_MysqlConnPool()
    {
        vec = new STL_Some[STL_MysqlConnPool::poolSize_];

        for (int i = 0; i < poolSize_; i++)
        {
            STL_Connection *conn = new STL_Connection(STL_MysqlConnPool::host_, STL_MysqlConnPool::port_, STL_MysqlConnPool::user_, STL_MysqlConnPool::passwd_, STL_MysqlConnPool::database_);
            //std::cout << conn << std::endl;
            //std::cout << host << " " << port << " " << user << " " << passwd << " " << database << " " << poolSize << std::endl;
            if (!conn)
            {
                cout << "xPool: new STL_Connection Operation failed" << endl;
            }
            vec[i].first = conn;
            vec[i].second = false;
        }
    }

    STL_MysqlConnPool::~STL_MysqlConnPool()
    {
        for (int i = 0; i < poolSize_; i++)
        {
            delete vec[i].first;
        }
        delete[] vec;
        //mysql_library_end();
    }

    int STL_MysqlConnPool::initPool(std::string host, unsigned int port, std::string user, std::string passwd, std::string database, int poolSize)
    {
        host_ = host;
        port_ = port;
        user_ = user;
        passwd_ = passwd;
        database_ = database;
        poolSize_ = poolSize;

        return 0;
    }

    void STL_MysqlConnPool::destroyPool()
    {
        if (pool_)
        {
            delete pool_;
            pool_ = NULL;
        }
    }

    STL_Connection *STL_MysqlConnPool::getConnection()
    {
        // init pool, open connections
        STL_MysqlConnPool::locki();
        if (pool_ == NULL)
        {
            pthread_mutex_init(&mutex, 0);
            pthread_mutex_init(&execmutex, 0);

            //mysql_library_init(0,NULL,NULL);

            pool_ = new STL_MysqlConnPool();
        }
        STL_MysqlConnPool::unlocki();

        //get connection operation
        STL_Connection *ret = NULL;
        while (true)
        {
            STL_MysqlConnPool::lock();
            bool flag = false;
            for (int i = 0; i < poolSize_; i++)
            {
                if (pool_->vec[i].second == false)
                {
                    pool_->vec[i].second = true;
                    ret = pool_->vec[i].first;
                    flag = true;
                    break;
                }
            }
            if (flag == true)
            {
                STL_MysqlConnPool::unlock();
                break;
            }
            else
            {
                //cout << "wait" << endl;
                STL_MysqlConnPool::unlock();
                usleep(1000);
                continue;
            }
        }
        return ret;
    }

    int STL_MysqlConnPool::releaseConnection(STL_Connection *conn)
    {
        lock();
        for (int i = 0; i < poolSize_; i++)
        {
            if (pool_->vec[i].first == conn)
            {
                pool_->vec[i].second = false;
                break;
            }
        }
        unlock();
        return 1;
    }

    STL_Connection::STL_Connection(std::string host, unsigned int port, std::string user, std::string passwd, std::string database)
    {
        // static int connectionCount = 0;
        //cout << "C#:" << connectionCount++ << endl;

        // 初始化连接
        conn = mysql_init(NULL);

        //cout << "conn:" << conn << endl;

        // 执行物理的tcp连接动作，完成三次握手
        if (!mysql_real_connect(conn, host.c_str(), user.c_str(), passwd.c_str(), database.c_str(), port, NULL, 0))
        {
            printf("xPool: Error connecting to database: %s\n", mysql_error(conn));
            exit(-1);
        }
        else
        {
        }
    }

    STL_Connection::~STL_Connection()
    {
        //mysql_thread_end();

        // 关闭TCP连接，四次挥手
        mysql_close(conn);
    }

    STL_QueryResult STL_Connection::executeQuery(std::string statement, long tid)
    {
        //STL_MysqlConnPool::locke();

        const char *query = statement.c_str();

        unsigned int len = (unsigned int)strlen(query);

        char q[1024];
        strncpy(q, query, len);
        q[len] = 0;

        printf("--------------------------------------------------\n");
        printf("%s\n", query);
        printf("--------------------------------------------------\n");

        int status = mysql_real_query(conn, q, len);

        if (status)
        {
            printf("Error making query: %s\n",
                   mysql_error(conn));
        }

        MYSQL_RES *resultSet;

        resultSet = mysql_store_result(conn);

        STL_QueryResult queryResult;

        queryResult.affected_rows = mysql_affected_rows(conn); //返回的行数改变/删除/插入的最后 UPDATE， DELETE或 INSERT查询。
        if (queryResult.affected_rows == ((my_ulonglong)-1))
            queryResult.affected_rows = 0;
        queryResult.error_number = mysql_errno(conn); //返回最近调用的MySQL函数的错误号。
        if (mysql_error(conn))
            queryResult.error_info = std::string(mysql_error(conn)); //返回有关最近调用的MySQL函数的错误消息。
        queryResult.insert_id = mysql_insert_id(conn);
        if (mysql_info(conn))
            queryResult.info = std::string(mysql_info(conn));
        if (mysql_sqlstate(conn))
            queryResult.sql_state = std::string(mysql_sqlstate(conn));
        queryResult.field_count = mysql_field_count(conn);
        if (queryResult.field_count != 0)
        {
            while (true)
            {
                MYSQL_FIELD *field;
                if (!(field = mysql_fetch_field(resultSet)))
                {
                    break;
                }
                queryResult.fields.push_back(std::string(field->name));
            }
            if (queryResult.fields.size() != queryResult.field_count)
            {
                printf("Error field count: %u,%lu\n", queryResult.field_count, queryResult.fields.size());
            }
            while (true)
            {
                MYSQL_ROW row;
                if (!(row = mysql_fetch_row(resultSet)))
                {
                    break;
                }

                std::vector<std::string> string_row;
                for (int i = 0; i < (int)mysql_num_fields(resultSet); i++)
                {
                    char *temp = row[i];
                    std::string tempData = "";
                    if (temp)
                    {
                        tempData = std::string(temp);
                    }
                    string_row.push_back(tempData);
                }
                if (string_row.size() != queryResult.field_count)
                {
                    printf("Error field count: %u,%lu\n", queryResult.field_count, string_row.size());
                }
                queryResult.string_table.push_back(string_row);
            }
        }

        mysql_free_result(resultSet); //free result after you get the result

        //STL_MysqlConnPool::unlocke();

        return queryResult;
    }
} // namespace STL